using System;
using System.Collections.Generic;
using System.Text;
using JobShopModel.Model.FactoryLvl;
using JobShopModel.Model.JobLvl;
using JobShopModel.Model.Station;

namespace JobShopModel.Model.JobLvl
{
    /// <summary>
    /// Reprensent a Job or Piece of materiel done by the factory.
    /// Each Job possess a given number of operation.
    /// Each operation must go to a specific station to be done.
    /// All the jobs are transported between the station by the transporter
    /// 
    /// Each job are created with the static method JobFactory  <see cref="JobFactory"/>
    /// and providing as argument the Type of the job <see cref="JobTypes"/>
    /// <example>Invocation: 
    ///     <code>myJob = Job.CreateJob(JobTypes.myType) </code>
    /// </example>
    /// 
    /// Element Recorded:
    /// the time of creation <see cref="StartTime"/>
    /// the time of completion <see cref="FinishTime"/>
    /// <seealso cref="Transport"/>
    /// <seealso cref="Operation"/>
    /// </summary>
    public class Job:IComparable<Job>
    {
        /// <summary>
        /// Time it got effectively created
        /// => It is handled by the station
        /// </summary>
        private Time startTime;
        
        private Time finishTime;

        private StationsName position;

        private List<Operation> listOperation;
        private Operation currentOperation;

        private Time totalWaitingFifo;

        private JobTypes type;

        private Time sQueueTime;

        //TODO: PLEASE NO PUBLIC MEMBERS !!!!

        /// <summary>
        /// Private constructor.
        /// </summary>
        private Job(Time start)
        {
            this.startTime = start;
            this.listOperation = new List<Operation>();
            this.totalWaitingFifo = new Time();
        }

        /// <summary>
        /// Static Method used for the creation of a Job,
        /// Let you use the constructor for the initialisation of elements and not the logic of Jobs Creations
        /// </summary>
        /// TODO: Create inheritance relation for each jobs and use polymorphism instead of enums.
        /// <param name="type">Type of job to be created</param>
        /// <returns>Instance of Job</returns>
        public static Job JobFactory(JobTypes type)
        {
            Job returnedJob = new Job(Factory.getInstance().CurrentTime);
            returnedJob.position = StationsName.IOSTATION;

            JobShopModel.Extern.Exponential exp = new JobShopModel.Extern.Exponential();

            switch (type)
            {
                case JobTypes.JOB1:

                    returnedJob.type = JobTypes.JOB1;
                    returnedJob.addOperation(StationsName.STATION3, new Time(0, exp.calculate(.25) + exp.calculate(.25)));
                    returnedJob.addOperation(StationsName.STATION1, new Time(0, exp.calculate(.3) + exp.calculate(.3)));
                    returnedJob.addOperation(StationsName.STATION2, new Time(0, exp.calculate(.425) + exp.calculate(.425)));
                    returnedJob.addOperation(StationsName.STATION5, new Time(0, exp.calculate(.25) + exp.calculate(.25)));
                    returnedJob.addOperation(StationsName.IOSTATION, new Time());

                    break;

                case JobTypes.JOB2:

                    returnedJob.type = JobTypes.JOB2;
                    returnedJob.addOperation(StationsName.STATION4, new Time(0, exp.calculate(.55) + exp.calculate(.55)));
                    returnedJob.addOperation(StationsName.STATION1, new Time(0, exp.calculate(.4) + exp.calculate(.4)));
                    returnedJob.addOperation(StationsName.STATION3, new Time(0, exp.calculate(.375) + exp.calculate(.375)));
                    returnedJob.addOperation(StationsName.IOSTATION, new Time());

                    break;

                case JobTypes.JOB3:

                    returnedJob.type = JobTypes.JOB3;
                    returnedJob.addOperation(StationsName.STATION2, new Time(0, exp.calculate(.6) + exp.calculate(.6)));
                    returnedJob.addOperation(StationsName.STATION5, new Time(0, exp.calculate(.125) + exp.calculate(.125)));
                    returnedJob.addOperation(StationsName.STATION1, new Time(0, exp.calculate(.35) + exp.calculate(.35)));
                    returnedJob.addOperation(StationsName.STATION4, new Time(0, exp.calculate(.45) + exp.calculate(.45)));
                    returnedJob.addOperation(StationsName.STATION3, new Time(0, exp.calculate(.5) + exp.calculate(.5)));
                    returnedJob.addOperation(StationsName.IOSTATION, new Time());

                    break;
            }
            return returnedJob;
        }

        /// <summary>
        /// Add an operation to the current job
        /// </summary>
        /// <param name="station">Station where the operation is done</param>
        /// <param name="time">Time taken by the operation</param>
        private void addOperation(StationsName station, Time time)
        {
            listOperation.Add(new Operation(station, time));
        }
        
        /// <summary>
        /// Return the first undone operation from the current job
        /// Set this operation as the CurrentOperation
        /// <seealso cref="Operation"/>
        /// <see cref="OperationStates"/>
        /// </summary>
        /// <returns>Operation do be performed</returns>
        public Operation SelectNextOperation()
        {
            Operation rtn = null;
            foreach (Operation o in listOperation)
            {
                if (o.State == OperationStates.UNDONE)
                {
                    this.currentOperation = o;
                    rtn = o;
                    break;
                }
            }
            return rtn;
        }

        /// <summary>
        /// Called to track statistics when job is waiting for transport
        /// </summary>
        /// <param name="start">Time started waiting</param>
        public void StartWaitingFIFO(Time start)
        {
            this.sQueueTime = start;
        }

        /// <summary>
        /// Called to update stastics when transport arrives
        /// </summary>
        /// <param name="end">Time of arrival</param>
        public void EndWaitingFIFO(Time end)
        {
            Time waitingTime = end - sQueueTime;
            this.totalWaitingFifo += waitingTime;
        }

        public override string ToString()
        {
            return string.Format("{0}/{1}", this.type, this.currentOperation);
        }

        #region property

        /// <summary>
        /// Time of creation of the job
        /// readonly must be set by the constructor
        /// </summary>
        public Time StartTime
        {
            get { return startTime; }
        }

        public JobTypes Type
        {
            get { return this.type; }
        }

        public Time TotalQueueTime
        {
            get
            {
                Time rtn = new Time();
                foreach (Operation op in listOperation)
                {
                    rtn += op.WaitingTime;
                }
                return rtn;
            }
        }

        public Time TotaBlockedTime
        {
            get
            {
                Time rtn = new Time();
                foreach (Operation op in listOperation)
                {
                    rtn += op.BlockedTime;
                }
                return rtn;
            }
        }

        /// <summary>
        /// Time the job is completed
        /// </summary>
        public Time FinishTime
        {
            get { return finishTime; }
            set { finishTime = value; }
        }

        public StationsName Position
        {
            get { return this.position; }
            set { this.position = value; }
        }

        public Operation CurrentOperation
        {
            get { return this.currentOperation; }
        }

        public Time TotalWaitingFifo
        {
            get { return this.totalWaitingFifo; }
        }

        #endregion property

        #region IComparable Membres

        public int CompareTo(Job other)
        {
            if (other.StartTime == this.StartTime)
            {
                return 0;
            }
            else if (other.StartTime > this.StartTime)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }

        public int CompareTo(object obj)
        {

            Job comp = (Job)obj;

            if (comp.StartTime == this.StartTime)
            {
                return 0;
            }
            else if (comp.StartTime > this.StartTime)
            {
                return 1;
            }
            else
            {
                return -1;
            }
 
        }

        #endregion
    
    }

    /// <summary>
    /// Types of the job
    /// </summary>
    public enum JobTypes 
    { 
        /// <summary>
        /// 
        /// </summary>
        JOB1= 0,
        /// <summary>
        /// 
        /// </summary>
        JOB2 = 1,
        /// <summary>
        /// 
        /// </summary>
        JOB3 = 2 
    }
}
